#ifndef _LIGHT_H_
#define _LIGHT_H_

#include "../d_Math/d_Math.h"
#include <cstdlib>

using namespace d_Math;

// ====================================================================
// ====================================================================

class Light {

public:

  // CONSTRUCTOR & DESTRUCTOR
  Light() {}
  virtual ~Light() {}

  // VIRTUAL METHODS
  virtual void getIllumination (const d_Vec3f &p, d_Vec3f &dir, d_Vec3f &col, 
				float &distanceToLight) const = 0;
  virtual void glInit(int id) = 0;

};

// ====================================================================
// ====================================================================

class UniformLight : public Light {

public:

  // CONSTRUCTOR & DESTRUCTOR
  UniformLight(const d_Vec3f &c){
  	color = c;
  }
  UniformLight() {}
  ~UniformLight() {}

  // VIRTUAL METHODS
  void getIllumination (const d_Vec3f &p, d_Vec3f &dir, d_Vec3f &col, 
				float &distanceToLight) const{
		dir = d_Vec3f(0,0,0);
		col = color;
		distanceToLight = INFINITY;		
  }
  void glInit(int id){}
private:
	d_Vec3f color;
};

// ====================================================================
// ====================================================================

class AreaLight : public Light {

public:

  // CONSTRUCTOR & DESTRUCTOR
  AreaLight(d_Vec3f p, d_Vec3f c, float width, float height, d_Vec3f a) 
  {
  	position = p;
  	color = c;
  	w = width;
  	h = height;
  	attenuation = a;
  }
  
  AreaLight() {}
  ~AreaLight() {}

  // VIRTUAL METHODS
  void getIllumination (const d_Vec3f &p, d_Vec3f &dir, d_Vec3f &col, float &distanceToLight) const
  {
		dir = (position + d_Vec3f((rand()/(float)RAND_MAX)*w-w/2,0,(rand()/(float)RAND_MAX)*h-h/2))-p;
		distanceToLight = len(dir);
		dir = norm(dir);
		float a = 1 / (attenuation[0] + 
			     attenuation[1]*distanceToLight + 
			     attenuation[2]*distanceToLight*distanceToLight);
		if (a < 0) a = 0;
		col = color*a;
  }
  void glInit(int id){}
private:
	d_Vec3f position;
	d_Vec3f color;
	d_Vec3f attenuation;
	float w;
	float h;
};

// ====================================================================
// ====================================================================

class DirectionalLight : public Light {

public:

  // CONSTRUCTOR & DESTRUCTOR
  DirectionalLight(const d_Vec3f &d, const d_Vec3f &c) {
    direction = d; direction = norm(direction);
    color = c; }
  ~DirectionalLight() {}

  // VIRTUAL METHODS
  void getIllumination (const d_Vec3f &p, d_Vec3f &dir, d_Vec3f &col, 
			float &distanceToLight) const {
    // the direction to the light is the opposite of the
    // direction of the directional light source
    dir = direction * (-1.0f);
    col = color;
    // the distance is INFINITY
    distanceToLight = INFINITY; 
  }

  void glInit(int id);

private:

  DirectionalLight(); //don't use

  // REPRESENTATION
  d_Vec3f direction;
  d_Vec3f color;

};

// ====================================================================
// ====================================================================

class PointLight : public Light {

public:

  // CONSTRUCTOR & DESTRUCTOR
  PointLight(const d_Vec3f &p, const d_Vec3f &c, float a1, float a2, float a3) {
    position = p; 
    color = c; 
    attenuation_1 = a1;
    attenuation_2 = a2;
    attenuation_3 = a3; }
  ~PointLight() {}

  // VIRTUAL METHODS
  void getIllumination (const d_Vec3f &p, d_Vec3f &dir, d_Vec3f &col, float &distanceToLight) const {
    dir = position - p;
    // grab the length before the direction is normalized
    distanceToLight = len(dir); 
    dir = norm(dir); 
    float attenuation = 1 / (attenuation_1 + 
			     attenuation_2*distanceToLight + 
			     attenuation_3*distanceToLight*distanceToLight);
    if (attenuation < 0) attenuation = 0;
    col = color * attenuation;
  }

  void glInit(int id);

private:

  PointLight(); // don't use

  // REPRESENTATION
  d_Vec3f position;
  d_Vec3f color;

  // attenuation of the light
  float attenuation_1;
  float attenuation_2;
  float attenuation_3;

};

class SpotLight : public Light {

public:

  // CONSTRUCTOR & DESTRUCTOR
  SpotLight(const d_Vec3f &p, const d_Vec3f &dir, const d_Vec3f &c, float r,  float a1) {
    position = p; 
    color = c; 
	direction = norm(dir);
    attenuation_1 = a1;
	radius = r;
	}
  ~SpotLight() {}

  // VIRTUAL METHODS
  void getIllumination (const d_Vec3f &p, d_Vec3f &dir, d_Vec3f &col, float &distanceToLight) const {
    dir = position - p;
    // grab the length before the direction is normalized
    distanceToLight = len(dir); 
    dir = norm(dir);
	float tmp = acos(dot(dir*(-1),direction));
	if(tmp<radius)
		col = color;
	else
		if(tmp>radius&&tmp<radius+attenuation_1)
			col = color*(1-(tmp-radius)/attenuation_1);
		else
			col = d_Vec3f(0,0,0);
  }

  void glInit(int id);

private:

  SpotLight(); // don't use

  // REPRESENTATION
  d_Vec3f position;
  d_Vec3f color;
  d_Vec3f direction;

  // attenuation of the light
  float attenuation_1;
  float radius;

};

// ====================================================================
// ====================================================================


#endif

